Skip to content

docs: add AI Teammates community example project - #5024

Closed
jackbridger wants to merge 1 commit into
triggerdotdev:mainfrom
jackbridger:docs/ai-teammates-community-example
Closed

jackbridger wants to merge 1 commit into
triggerdotdev:mainfrom
jackbridger:docs/ai-teammates-community-example

Conversation

@jackbridger

@jackbridger jackbridger commented Oct 13, 2026 •

Copy link
Copy Markdown

Summary

Adds a new example project page for AI Teammates, a community-built open-source Grok Bot clone by Code with Antonio: https://github.com/code-with-antonio/ai-teammates

The project uses a Trigger.dev chat.agent() for each teammate's durable turn, a scheduled task to meter sandbox hours, Daytona sandboxes as each teammate's computer, and a Next.js frontend on useTriggerChatTransport. The author has given permission for us to link it from our docs and examples.

Changes

  • New page docs/guides/example-projects/ai-teammates.mdx covering the chat agent (per-turn tools, handoffs in group chats, step and spend limits), the sandbox and memory tools, usage metering via onTurnComplete and a cron task, the Sentry init file with global hooks, and the frontend wiring. Code snippets are taken from the repo and link back to it.
  • Added to the example projects navigation in docs/docs.json.
  • Added a row to the example projects table in docs/guides/introduction.mdx.

Companion PR in the examples repo: triggerdotdev/examples#154

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Oct 13, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: a568af5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 95887501-6141-4d28-b7f5-497087b7cccf
📥 Commits

Reviewing files that changed from the base of the PR and between 3d54889 and a568af5.

📒 Files selected for processing (3)
  • docs/docs.json
  • docs/guides/example-projects/ai-teammates.mdx
  • docs/guides/introduction.mdx
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Hi @jackbridger, thanks for your interest in contributing!

This project requires that pull request authors are vouched, and you are not in the list of vouched users.

This PR will be closed automatically. See https://github.com/triggerdotdev/trigger.dev/blob/main/CONTRIBUTING.md for more details.

@github-actions github-actions Bot closed this Oct 13, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 5 potential issues.

Devin Review

startSession: ({ chatId, clientData }) => startChatSession({ chatId, clientData }),
});
const { messages, isLoading } = useLoadTranscript(chatId, loadTranscript, { transport });
// Then useChat({ transport, messages }) once the transcript has loaded

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Restored chats cannot send messages

After loading a transcript, useChat({ transport, messages }) generates a new ID instead of using chatId. startChatSession rejects that ID because the user owns the original chat, so sending fails.

Suggested change
// Then useChat({ transport, messages }) once the transcript has loaded
// Then useChat({ id: chatId, transport, messages }) once the transcript has loaded

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +106 to +108
chat.setUIMessageStreamOptions({
messageMetadata: () => ({ botId: bot.id }),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Handoff replies show the original teammate

When a group turn hands off, messageMetadata still stamps the initial bot.id on the reply. The UI identifies replies by that metadata, so it credits the wrong teammate.

Learn more

The group's handoff callback changes current inside the tools callback, but run captures a separate initial bot. Its metadata callback never observes a handoff. The UI uses that metadata to label the response. Changing the callback to consult a mutable variable requires sharing turn state with the tools callback and checking when the AI SDK emits metadata; metadata emitted at response start cannot be changed retroactively.

Example: A turn starts with Alice and invokes handoff to Bob. Bob's sandbox tools and instructions take over, but the reply remains labeled Alice.

Recommended fix: Coordinate the selected bot across the per-turn tools and run callbacks, and emit/update reply metadata when handoff completes. Test that a group turn with a handoff renders the answering bot's identity.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +244 to +251
await recordUsage(
owned.map(({ sandboxId, userId }) => ({
userId,
kind: "sandbox" as const,
amount: INTERVAL_MINUTES * 60,
// A retried run meters the same moment again
idempotencyKey: `sandbox:${sandboxId}:${timestamp.toISOString()}`,
}))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 New sandboxes incur full interval charges

If a sandbox starts just before the schedule runs, meterSandboxes charges it 300 seconds immediately. Its actual running time can be seconds, so the ledger overstates usage and can exhaust the quota early.

Learn more

Each run lists sandboxes currently running and inserts a fixed five-minute usage entry for each. The sample does not track when a sandbox started or how long it was running since the previous tick. The first tick after a sandbox starts therefore charges a full interval even if the sandbox just started, while a sandbox that starts and stops between ticks is not billed at all.

Example: A sandbox starts at 12:04:59 and is still running at the 12:05:00 tick. The ledger records 300 seconds although it ran for one second before the tick.

Recommended fix: Meter elapsed running time from sandbox start/stop events or durable per-sandbox timestamps; cap each tick's charge to the actual overlap with the interval, and record completed running periods that end between ticks.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.


The `tools` callback runs each turn with the `chatId` and the client data. It loads the chat's bots, wires up the sandbox tools for the bot holding the turn, and in a group chat adds a `handoff` tool that moves the turn, and the sandbox, to a teammate:

```ts trigger/chat.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 TypeScript examples use nonstandard fence tags

The documentation convention specifies typescript fences, but this page uses ts and tsx throughout. Align the tags for consistent syntax highlighting.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

id: "chat",
// Declared here so screenshots stay images when earlier turns are replayed
tools: async ({ chatId, clientData }) => {
const { chat: { kind, userId }, bots, bot } = await getChatBots(chatId, clientData?.botId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟥 Client-selected teammate risks cross-chat sandbox access

If getChatBots accepts a botId outside the owned chat, browser-controlled clientData.botId selects that bot's sandbox. The session's chat ownership check cannot protect a different bot's computer.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant